package org.vaadin.nf4v.core.handler.impl;

import org.vaadin.nf4v.core.handler.ParamResolver;
import org.vaadin.nf4v.core.navigation.InvalidUriException;
import org.vaadin.nf4v.core.navigation.target.NavigationTarget;

import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * Default ParamResolver implementation.
 * The only configuration available is separator character between parameters.
 * By default it's "/".
 * Encodes named parameters as key=value.
 *
 * In time of parameters parsing from uri string, unnamed parameters being named as
 * order parameter order number, starting from 1.
 *
 * E.g. name=sample/134/lang=en ==> [name:sample, 2:134, lang:en]
 *
 * In time of preparing of parameters string, natural order of entries in map is used to
 * order parameters. Also all digital named parameters are treated as unnamed.
 *
 * E.g. [name:sample, 32:value, lang:en] ==> name=sample/value/lang=en
 *
 * All values are parsed as strings.
 * To parse parameters as correct objects (e.g. prefetch entity by id) derived implementation must
 * override parseParameter and toStringParameter methods.
 */
public class DefaultParamResolver implements ParamResolver {

    // separator character between parameters
    private String separator;
    private Pattern numberPattern = Pattern.compile("^\\d+$");

    public DefaultParamResolver(String separator) {
        this.separator = separator;
    }

    @Override
    public void resolveParameters(NavigationTarget target) throws InvalidUriException {
        String paramString = target.getParamsString();
        LinkedHashMap<String, Serializable> params = new LinkedHashMap<String, Serializable>();
        target.setParams(params);
        if (paramString == null || "".equals(paramString.trim())) {
            return;
        }
        int i = 1;
        for (String chunk : paramString.split(separator)) {
            String[] par = chunk.split("=");
            if (par.length > 2) {
                throw new InvalidUriException("Parameter '" + chunk + "' is invalid", false, true);
            }
            if (par.length == 1) {
                String name = String.valueOf(i);
                params.put(name, parseParameter(name, chunk));
            } else {
                params.put(par[0], parseParameter(par[0], par[1]));
            }
            i++;
        }
    }

    /**
     * Default implementation did nothing.
     * Override this to convert parameters into objects.
     *
     * @param name parameter name (may be digital, representing order number)
     * @param value parameter value
     * @return parsed parameter value.
     */
    protected Serializable parseParameter(String name, String value) {
        return value;
    }

    @Override
    public void prepareParametersString(NavigationTarget target) {
        LinkedHashMap<String, Serializable> params = target.getParams();
        if (params == null || params.size() == 0) {
            return;
        }
        String[] paramChunks = new String[params.size()];
        int i=1;
        for(Map.Entry<String, Serializable> entry:params.entrySet()){
            if (numberPattern.matcher(entry.getKey()).matches()){
                paramChunks[i-1]=toStringParameter(entry.getKey(), entry.getValue());
            } else {
                paramChunks[i-1]=entry.getKey()+"="+toStringParameter(entry.getKey(), entry.getValue());
            }
            i++;
        }

        String paramString = "";
        i=0;
        for(String chunk: paramChunks){
            if (i++>0){
                paramString+=separator;
            }
            paramString+=chunk;
        }
        target.setParamsString(paramString);
    }

    /**
     * Default imlementation just calls toString on objects.
     * Override this to correctly convert parameters into string.
     *
     * @param name parameter name
     * @param value parameter object value
     * @return string representation of value.
     */
    protected String toStringParameter(String name, Object value){
        return value.toString();
    }
}
